package com.scheduler.backend.aps.deadlinefirst;

import com.scheduler.backend.aps.domain.deadlinefirst.OrderSlice;
import com.scheduler.backend.aps.domain.deadlinefirst.ScheduleTable;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;

import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author gfy
 */
public class ScheduleTableConstraintProvider implements ConstraintProvider {
    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[]{
                // Hard constraints
                ShiftCraftTimeConflict(constraintFactory),
                IdConflict(constraintFactory),
                CraftGapConflict(constraintFactory),
                ResourceTimeConflict(constraintFactory),
                OutOfTimeConflict(constraintFactory),
                DeviceUseRateConflict(constraintFactory),
                EmployeeUseRateConflict(constraintFactory)
        };
    }

    //上班时间不能和工艺时间冲突
    private Constraint ShiftCraftTimeConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.from(OrderSlice.class).filter(OrderSlice::shiftCraftTimeConflict)
                .penalize("ShiftCraftTimeConflict", HardSoftScore.ONE_HARD);
    }

    //工艺必须按照顺序来
    private Constraint IdConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.from(OrderSlice.class).join(
                OrderSlice.class)
                .filter((p1, p2) -> {
                    if (p1.getId().equals(p2.getId())) {
                        return false;
                    }
                    if (!p1.getOrderId().equals(p2.getOrderId())) {
                        return false;
                    }
                    if (p1.getItemIndex().equals(p2.getItemIndex())) {
                        if (p1.getCraftIndex() > p2.getCraftIndex()) {
                            return p1.getStartTimeslot() <= p2.getStartTimeslot();
                        }
                    } else {
                        if (p1.isLastCraft() && p2.isLastCraft()) {
                            if (p1.getItemIndex() > p2.getItemIndex()) {
                                return p1.getStartTimeslot() <= p2.getStartTimeslot();
                            }
                        }
                    }
                    return false;
                })
                .penalize("IdConflict", HardSoftScore.ONE_HARD);
        //return null;
    }

    //同订单不同工艺 或者 不同订单 必须有 0.1H 间隔
    private Constraint CraftGapConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.from(OrderSlice.class).join(
                OrderSlice.class)
                .filter((p1, p2) -> {
                            if (p1.getId().equals(p2.getId())) {
                                return false;
                            }
                            //如果生产的是同一个物品,而且是同一个工艺步骤,直接返回false
                            if (p1.getProduct().getProductId().equals(p2.getProduct().getProductId())
                                    && p1.getCraftIndex().equals(p2.getCraftIndex())
                            ) {
                                return false;
                            }

                            boolean pd = false;
                            if (p1.getEquipments() != null && p2.getEquipments() != null && !"null".equals(p1.getEquipments().getGroupId()) && !"null".equals(p2.getEquipments().getGroupId())) {
                                if (p1.getEquipments().getGroupId().equals(p2.getEquipments().getGroupId())) {
                                    pd = true;
                                }
                            }
                            if (p1.getEmployees() != null && p2.getEmployees() != null) {
                                for (int i = 0; i < p1.getEmployees().getEmployeeGroups().size(); i++) {
                                    for (int j = 0; j < p2.getEmployees().getEmployeeGroups().size(); j++) {
                                        if (p1.getEmployees().getEmployeeGroups().get(i).getAmount() == 0) {
                                            continue;
                                        }
                                        if (p2.getEmployees().getEmployeeGroups().get(j).getAmount() == 0) {
                                            continue;
                                        }
                                        if (p1.getEmployees().getEmployeeGroups().get(i).getResourceId().equals(p2.getEmployees().getEmployeeGroups().get(j).getResourceId())) {
                                            pd = true;
                                            break;
                                        }
                                    }
                                    if (pd) {
                                        break;
                                    }
                                }
                            }

                            if (pd) {
                                Long minTimeEnd = -1L;
                                Long maxTimeStart = -1L;
                                if (p1.getStartTimeslot() + ScheduleTable.ORDER_SLICE_TIMESLOT_SPAN - 1 > p2.getStartTimeslot() + ScheduleTable.ORDER_SLICE_TIMESLOT_SPAN - 1) {
                                    minTimeEnd = p2.getStartTimeslot() + ScheduleTable.ORDER_SLICE_TIMESLOT_SPAN - 1;
                                    maxTimeStart = p1.getStartTimeslot();
                                } else {
                                    minTimeEnd = p1.getStartTimeslot() + ScheduleTable.ORDER_SLICE_TIMESLOT_SPAN - 1;
                                    maxTimeStart = p2.getStartTimeslot();
                                }
                                //修改为无间隔 或者 0.1H 间隔
                                return maxTimeStart - minTimeEnd < 0;
                            } else {
                                return false;
                            }

                        }
                )
                .penalize("CraftGapConflict", HardSoftScore.ONE_HARD);
    }

    //一个资源只能属于一个时间片 不能出现交叉
    private Constraint ResourceTimeConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.from(OrderSlice.class).join(
                OrderSlice.class)
                .filter((p1, p2) -> {
                    if (p1.getId().equals(p2.getId())) {
                        return false;
                    }
                    boolean pd = false;
                    if (p1.getEquipments() != null && p2.getEquipments() != null && !"null".equals(p1.getEquipments().getGroupId()) && !"null".equals(p2.getEquipments().getGroupId())) {
                        if (p1.getEquipments().getGroupId().equals(p1.getEquipments().getGroupId())) {
                            pd = true;
                        }
                    }
                    if (p1.getEmployees() != null && p2.getEmployees() != null) {
                        for (int i = 0; i < p1.getEmployees().getEmployeeGroups().size(); i++) {
                            for (int j = 0; j < p2.getEmployees().getEmployeeGroups().size(); j++) {
                                if (p1.getEmployees().getEmployeeGroups().get(i).getAmount() == 0) {
                                    continue;
                                }
                                if (p2.getEmployees().getEmployeeGroups().get(j).getAmount() == 0) {
                                    continue;
                                }
                                if (p1.getEmployees().getEmployeeGroups().get(i).getResourceId().equals(p2.getEmployees().getEmployeeGroups().get(j).getResourceId())) {
                                    pd = true;
                                    break;
                                }
                            }
                            if (pd) {
                                break;
                            }
                        }
                    }
                    if (pd) {//首先排除不交叉的情况
                        if (p1.getStartTimeslot() + ScheduleTable.ORDER_SLICE_TIMESLOT_SPAN - 1 < p2.getStartTimeslot()) {
                            return false;
                        }
                        if (p2.getStartTimeslot() + ScheduleTable.ORDER_SLICE_TIMESLOT_SPAN - 1 < p1.getStartTimeslot()) {
                            return false;
                        }
                        //剩余情况即为交叉
                        return true;
                    } else {
                        return false;
                    }

                })
                .penalize("ResourceTimeConflict", HardSoftScore.ONE_HARD);
    }


    private Constraint OutOfTimeConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.from(OrderSlice.class).filter((p1) -> {
                    LocalDateTime ddl = p1.getOrder().getDeadline();
                    LocalDateTime finishTime = p1.getScheduleStartTime().plusSeconds((p1.getStartTimeslot() + ScheduleTable.ORDER_SLICE_TIMESLOT_SPAN) * 60 * ScheduleTable.ORDER_SLICE_MINUTES);
                    if (p1.isLastItem() && p1.isLastCraft()) {
                        return !ddl.isBefore(finishTime);
                    } else {
                        return false;
                    }
                }
        ).reward("NotOutOfTime", HardSoftScore.ONE_SOFT, (p1)->{return 100000;});
    }

    private Constraint DeviceUseRateConflict(ConstraintFactory constraintFactory){//设备利用率平均 by gfy
        return constraintFactory.from(OrderSlice.class)
                //.groupBy(loadBalance(OrderSlice::getEquipments))
                .penalize("DeviceUseRateConflict", HardSoftScore.ONE_SOFT,
                        DeviceLoadBalanceData::getZeroDeviationSquaredSumRootMillis
                        );
    }

    private Constraint EmployeeUseRateConflict(ConstraintFactory constraintFactory){//员工利用率平均 by gfy
        return constraintFactory.from(OrderSlice.class)
                //.groupBy(loadBalance(OrderSlice::getEquipments))
                .penalize("EmployeeUseRateConflict", HardSoftScore.ONE_SOFT,
                        EmployeeLoadBalanceData::getZeroDeviationSquaredSumRootMillis
                );
    }


    public static final class DeviceLoadBalanceData {


        private static final Map<String, Object> idGroupMap = new LinkedHashMap<>(0);

        private static final Map<Object, Integer> groupCountMap = new LinkedHashMap<>(0);

        // the sum of squared deviation from zero

        private static int squaredSum = 0;

        private static int count = 0;

        private static int maxCount = 0;

        public static void setMaxCount(int x){
            maxCount = x;
        }

        public static int getMaxCount(){
            return maxCount;
        }

        public static void calculate(OrderSlice orderSlice){
            String id = orderSlice.getId();
            Object mapped = orderSlice.getEquipments();
            Object oldMapped = null;
            if (idGroupMap.containsKey(id)){
                oldMapped = idGroupMap.get(id);
            }

            if (oldMapped == null){
                idGroupMap.put(id,mapped);
            }else{
                Integer oldValue = 0;
                if (groupCountMap.containsKey(oldMapped)){
                    oldValue = groupCountMap.get(oldMapped);
                    oldValue --;
                    if (oldValue <= 0){
                        groupCountMap.remove(oldMapped);
                    }else{
                        groupCountMap.put(oldMapped,oldValue);
                    }
                }
                squaredSum -= 2 * (oldValue + 1) - 1;
                idGroupMap.put(id,mapped);
            }


            Integer value = 0;
            if (groupCountMap.containsKey(mapped)){
                value = groupCountMap.get(mapped);
                value++;
                groupCountMap.put(mapped,value);
            }else{
                value = 1;
                groupCountMap.put(mapped,value);
            }
            squaredSum += (2 * value - 1);


        }


        public static int getZeroDeviationSquaredSumRootMillis(OrderSlice orderSlice) {
            calculate(orderSlice);
            if (maxCount == 0) maxCount=1;
            //int ans = (int) (Math.sqrt((double) squaredSum) / (double)maxCount * 10);
            int ans = squaredSum;
            return ans;
        }

    }

    public static final class EmployeeLoadBalanceData {


        private static final Map<String, Object> idGroupMap = new LinkedHashMap<>(0);

        private static final Map<Object, Integer> groupCountMap = new LinkedHashMap<>(0);

        // the sum of squared deviation from zero

        private static int squaredSum = 0;

        private static int count = 0;

        private static int maxCount = 0;

        public static void setMaxCount(int x){
            maxCount = x;
        }

        public static int getMaxCount(){
            return maxCount;
        }

        public static void calculate(OrderSlice orderSlice){
            int i=0;
            if (squaredSum>100){
                i=1;
            }
            String id = orderSlice.getId();
            Object mapped = orderSlice.getEmployees();
            Object oldMapped = null;
            if (idGroupMap.containsKey(id)){
                oldMapped = idGroupMap.get(id);
            }

            if (oldMapped == null){
                idGroupMap.put(id,mapped);
            }else{
                Integer oldValue = 0;
                if (groupCountMap.containsKey(oldMapped)){
                    oldValue = groupCountMap.get(oldMapped);
                    oldValue --;
                    if (oldValue <= 0){
                        groupCountMap.remove(oldMapped);
                    }else{
                        groupCountMap.put(oldMapped,oldValue);
                    }
                }
                squaredSum -= 2 * (oldValue + 1) - 1;
                idGroupMap.put(id,mapped);
            }


            Integer value = 0;
            if (groupCountMap.containsKey(mapped)){
                value = groupCountMap.get(mapped);
                value++;
                groupCountMap.put(mapped,value);
            }else{
                value = 1;
                groupCountMap.put(mapped,value);
            }
            squaredSum += (2 * value - 1);


        }


        public static int getZeroDeviationSquaredSumRootMillis(OrderSlice orderSlice) {
            calculate(orderSlice);
            if (maxCount == 0) maxCount=1;
            //int ans = (int) (Math.sqrt((double) squaredSum) / (double)maxCount * 10);
            int ans = squaredSum;
            return ans;
        }

    }
}